#!/usr/bin/env python
from __future__ import with_statement

import sys
import os, os.path
import logging
from StringIO import StringIO

import unittest

import BeautifulSoup

from pyinstaweb import *

TEST_URL = 'http://www.google.com'

TEST_HTML = """
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<body onload="alert('hello&amp;test')" style='bgcolor:black'>
    <H1>Hello World</H1>
    <br/>
    <![CDATA[Testing Data]]>
    <!--Testing Comment-->
    <!--[if IE 8]>
    <p>Welcome to Internet Explorer 8.</p>
    <![endif]-->
</body>
</html>"""

class TestFilter(object):
    def __init__(self, testcase):
        self.testcase = testcase

        self.startdoc = 0
        self.enddoc = 0
        self.starttags = []
        self.endtags = []
        self.cdatas = []
        self.comments = []
        self.decls = []
        self.iedecls = []
        self.data = []

    def handle_startdoc(self):
        self.startdoc += 1

    def handle_endoc(self):
        self.enddoc += 1

    def handle_starttag(self, tag):
        self.starttags.append(tag)

        self.testcase.assert_(tag.rewritable)

    def handle_endtag(self, tag):
        self.endtags.append(tag)

    def handle_cdata(self, cdata):
        self.cdatas.append(cdata)

    def handle_comment(self, comment):
        self.comments.append(comment)

    def handle_decl(self, decl):
        self.decls.append(decl)

    def handle_ie_decl(self, decl):
        self.iedecls.append(decl)

    def handle_data(self, data):
        self.data.append(data)

    def __repr__(self):
        return "tags: %s\ndata: %s\n" % (
            [tag.tagName for tag in self.starttags],
            [data.contents for data in self.data]
        )

class ParserTestCase(unittest.TestCase):
    def testInit(self):
        parser = HtmlParser(TEST_URL, 'id')

        self.assertEquals(TEST_URL, parser.url)
        self.assertEquals('id', parser.id)
        self.assertEquals(1, parser.linenum)

    def testParse(self):
        parser = HtmlParser(TEST_URL, 'id')

        parser.feed(TEST_HTML)

        self.assertEquals(1, parser.linenum)

        parser.flush()

        self.assertEquals(14, parser.linenum)

        parser.close()

        self.assertEquals(14, parser.linenum)

        parser.reset()

        self.assertEquals(1, parser.linenum)

    def parse(self, html):
        filter = TestFilter(self)
        parser = HtmlParser(TEST_URL)
        parser.addFilter("test", filter)

        parser.feed(html)
        parser.flush()

        filter.parser = parser

        return filter

    def _testMalformPage(self):
        filter = self.parse("<table/**/style=position:absolute;clip:rect(0)>")

        self.assertEquals(1, len(filter.starttags))

class FilterTestCase(unittest.TestCase):
    def testLifecycle(self):
        filter = TestFilter(self)

        with HtmlParser(TEST_URL) as parser:
            parser.addFilter("test", filter)

            parser.feed(TEST_HTML)

            parser.flush()

            adapter = parser.adapters["test"]

            self.assertEquals(filter, adapter.handler)
            self.assertEquals('test', adapter.name)

        self.assertEquals(1, filter.startdoc)
        self.assertEquals(1, filter.enddoc)
        self.assertEquals(4, len(filter.starttags))
        self.assertEquals(4, len(filter.endtags))

        self.assertEquals(1, len(filter.cdatas))
        self.assertEquals(1, len(filter.comments))
        self.assertEquals(1, len(filter.decls))
        self.assertEquals(1, len(filter.iedecls))
        self.assertEquals(11, len(filter.data))

    def testHtmlWriterFilter(self):
        out = StringIO()

        with HtmlParser(TEST_URL) as parser:
            parser.addFilter("dump", HtmlWriterFilter(parser, out))

            parser.feed(TEST_HTML)

        self.assertEquals(TEST_HTML.replace('H1', 'h1'), out.getvalue())

class NodeTestCase(unittest.TestCase):
    def setUp(self):
        self.filter = TestFilter(self)
        self.parser = HtmlParser(TEST_URL)
        self.parser.addFilter("test", self.filter)
        self.parser.feed(TEST_HTML)
        self.parser.flush()

    def tearDown(self):
        self.parser.close()

    def testNode(self):
        tagHtml, tagBody, tagH1, tagBr = self.filter.starttags

        self.assertEquals("html", tagHtml.tagName)
        self.assertEquals("body", tagBody.tagName)
        self.assertEquals("h1", tagH1.tagName)
        self.assertEquals("br", tagBr.tagName)

        self.assert_(tagHtml.alive)
        self.assertFalse(tagHtml.rewritable)

        self.assertEquals(tagHtml, tagBody.parent)
        self.assertEquals(tagBody, tagH1.parent)

        self.assertEquals(CloseStyle.brief, tagBr.closeStyle)

        self.assertEquals("Testing Data", self.filter.cdatas[0].contents)
        self.assertEquals("Testing Comment", self.filter.comments[0].contents)
        self.assertEquals("""DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"""", self.filter.decls[0].contents)
        self.assertEquals("""[if IE 8]>
    <p>Welcome to Internet Explorer 8.</p>
    <![endif]""", self.filter.iedecls[0].contents)

        self.assertEquals(['\n', '\n', '\n', '\n    ', 'Hello World', '\n    ',
                           '\n    ', '\n    ', '\n    ', '\n', '\n'],
                          [data.contents for data in self.filter.data])

    def testElement(self):
        tagHtml, tagBody, tagH1, tagBr = self.filter.starttags

        self.assertEquals("html", tagHtml.tagName)
        self.assertEquals(CloseStyle.explicit, tagHtml.closeStyle)
        self.assertEquals(4, tagHtml.beginLineNum)
        self.assertEquals(14, tagHtml.endLineNum)

        self.assertEquals(2, tagBody.attributeCount)

        attrOnload = tagBody.getAttribute(0)
        attrStyle = tagBody.getAttribute(1)

        self.assertEquals('onload', attrOnload.name)
        self.assertEquals("alert('hello&test')", attrOnload.value)
        self.assertEquals("alert('hello&amp;test')", attrOnload.escapedValue)
        self.assertEquals('"', attrOnload.quote)

        self.assertEquals('bgcolor:black', tagBody['style'])
        self.assertEquals('bgcolor:black', tagBody[1])

        self.assertEquals(attrOnload, tagBody.findAttribute('onload'))
        self.assertEquals(None, tagBody.findAttribute('onunload'))

        self.assertEquals(attrStyle.value, tagBody.getAttributeValue('style'))

        tagBody.addAttribute('onunload', 'test')

        self.assertEquals(3, tagBody.attributeCount)

        attrUnload = tagBody.findAttribute('onunload')

        self.assertEquals(attrUnload.value, tagBody.getAttributeValue('onunload'))

        tagBody.deleteAttribute('onunload')

        self.assertEquals(None, tagBody.findAttribute('onunload'))

        self.assertEquals(2, tagBody.attributeCount)

    def testContainer(self):
        tagHtml, tagBody, tagH1, tagBr = self.filter.starttags

        tagBody['onunload'] = 'test'
        tagBody[2] = 5

        self.assert_('onunload' in tagBody)
        self.assertEquals('5', tagBody['onunload'])

        del tagBody[2]

        self.assert_('onunload' not in tagBody)
        self.assertEquals(None, tagBody['onunload'])

        tagBody['onunload'] = 'test'

        self.assertEquals('test', tagBody['onunload'])

        del tagBody['onunload']

        self.assertEquals(None, tagBody['onunload'])

        self.assertEquals(2, len(tagBody))

        try:
            tagBody[10] = 'test'

            self.fail()
        except IndexError:
            pass

        try:
            del tagBody['nonexists']

            self.fail()
        except KeyError:
            pass

        try:
            del tagBody[20]

            self.fail()
        except IndexError:
            pass

class DocumentTestCase(unittest.TestCase):
    def setUp(self):
        self.parser = HtmlParser(TEST_URL)

        self.doc = self.parser.document

        self.node1 = self.doc.addNode(self.doc.createTextNode('1'))
        self.node2 = self.doc.createTextNode('2')
        self.node3 = self.doc.createTextNode('3')

    def tearDown(self):
        self.parser.close()

    def checkExpected(self, text):
        out = StringIO()

        self.parser.applyFilter(HtmlWriterFilter(self.parser, out))

        self.assertEquals(text, out.getvalue())

    def testCreateNodes(self):
        html = self.doc.createElement('html')

        self.assert_(html)
        self.assertEquals('html', html.tagName)
        self.assertEquals(None, html.parent)
        self.assertFalse(html.rewritable)

        cdata = self.doc.createCDATASection('value', html)

        self.assert_(cdata)
        self.assertEquals('value', cdata.contents)
        self.assertEquals(html, cdata.parent)

        text = self.doc.createTextNode('value', html)

        self.assert_(text)
        self.assertEquals('value', text.contents)
        self.assertEquals(html, text.parent)

        comment = self.doc.createComment('value', html)

        self.assert_(comment)
        self.assertEquals('value', comment.contents)
        self.assertEquals(html, comment.parent)

        decl = self.doc.createDirective('value', html)

        self.assert_(decl)
        self.assertEquals('value', decl.contents)
        self.assertEquals(html, decl.parent)

        decl = self.doc.createIEDirective('value', html)

        self.assert_(decl)
        self.assertEquals('value', decl.contents)
        self.assertEquals(html, decl.parent)

    def testReplace(self):
        self.doc.replaceNode(self.node1, self.node2)

        self.checkExpected("2")

    def _testInsertElementBeforeElement(self):
        self.assert_(self.doc.insertBefore(self.node2, self.node1))

        self.checkExpected("21")

        self.assert_(self.doc.insertBefore(self.node3, self.node1))

        self.checkExpected("321")

    def testDomManipulation(self):
        self.checkExpected("1")

        div = self.doc.createElement('div')

        self.doc.addElement(div, 123)

        self.assertEquals(123, div.beginLineNum)

        self.doc.addNode(self.node2).parent = div

        self.doc.closeElement(div, 456, CloseStyle.explicit)

        self.assertEquals(456, div.endLineNum)

        self.doc.addNode(self.node3)

        self.checkExpected("1<div>2</div>3")

class TextExtractorTestCase(unittest.TestCase):
    def testPlainText(self):
        result = extract_text("<html><body>hello</body></html>")

        self.assertEquals("hello", str(result))
        self.assertEquals("/html/body: hello", repr(result))

        result = extract_text("<html><body><a href='test' name='id'>hello</a></body></html>")

        self.assertEquals("idhello", str(result))
        self.assertEquals("/html/body/a[@name]: id,/html/body/a: hello", repr(result))

        self.assertEquals("/html/body/a[@name]=id,/html/body/a=hello", ','.join(["%s=%s" % (tag.xpath, text) for tag, text in result]))

        result = extract_link("<html><body><a href='test' name='id'>hello</a><img src='img'></body></html>")
        self.assertEquals("testimg", str(result))
        self.assertEquals("/html/body/a[@href]: test,/html/body/img[@src]: img", repr(result))

class BeautifulSoupTestCase(unittest.TestCase):
    def testParse(self):
        dom = BeautifulSoup.BeautifulSoup(TEST_HTML, builder=HTMLParserBuilder)

        self.assertEquals("""
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<body onload="alert(\'hello&amp;test\')" style="bgcolor:black">
<h1>Hello World</h1>
<br />
<![CDATA[Testing Data]]>
<!--Testing Comment-->
<!--[if IE 8]>
    <p>Welcome to Internet Explorer 8.</p>
    <![endif]-->
</body>
</html>""", str(dom))

    def doPerformanceTest(self, name, builder, html, rounds=1):
        import time

        start = time.clock()

        for i in range(rounds):
            if builder:
                dom = BeautifulSoup.BeautifulSoup(html, builder=builder)
            else:
                with HtmlParser(DEFAULT_URL) as parser:
                    parser.feed(html)

                    parser.flush()

        interval = time.clock() - start

        print "BeautifulSoup w/ %s parser in %.2fs (%.2f KB/s)" % (name, interval, len(html)*rounds/1024/interval)

    def _testPerformance(self):
        html = open(os.path.join(os.path.dirname(__file__), "../../HTML5.htm"), "r").read()

        self.doPerformanceTest('build-in', BeautifulSoup.HTMLParserBuilder, html)
        self.doPerformanceTest('pyinstaweb', HTMLParserBuilder, html)
        self.doPerformanceTest('pyinstaweb raw', None, html)

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG if "-v" in sys.argv else logging.WARN,
                        format='%(asctime)s %(levelname)s %(message)s')

    unittest.main()
